﻿// <copyright file="EventXmlSerializer.cs" company="http://www.dymetis.com">
//
// Copyright (c) 2011 Eddie Z. Lin.
//
// This source is subject to the Microsoft Public License.
// See http://www.opensource.org/licenses/ms-pl.
// All other rights reserved.
// 
// </copyright>

namespace Dymetis.TeamAlert
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Serialization;
    using Microsoft.TeamFoundation.Build.Server;
    using Microsoft.TeamFoundation.Framework.Server;
    using Microsoft.TeamFoundation.WorkItemTracking.Server;

    internal sealed class EventXmlSerializer
    {
        private static readonly XmlSerializer workItemChangedEventSerializer = new XmlSerializer(typeof(WorkItemChangedEvent));
        private static readonly XmlSerializer buildCompletionEvent2Serializer = new XmlSerializer(typeof(BuildCompletionEvent2));
        private static readonly XmlSerializer buildCompletionEventSerializer = new XmlSerializer(typeof(BuildCompletionEvent));

        private readonly TeamFoundationRequestContext requestContext;

        public EventXmlSerializer(TeamFoundationRequestContext requestContext)
        {
            this.requestContext = requestContext;
        }

        public XDocument Serialize<T>(object notificationEventArgs)
            where T : class
        {
            if (typeof(T) == typeof(BuildCompletionEvent2))
            {
                return this.GenerateBuildCompletionEvent2Xml(notificationEventArgs);
            }

            if (typeof(T) == typeof(BuildCompletionEvent))
            {
                return this.GenerateBuildCompletedEventXml(notificationEventArgs);
            }

            if (typeof(T) == typeof(WorkItemChangedEvent))
            {
                return this.GenerateWorkItemChangedEventXml(notificationEventArgs);
            }

            throw new NotSupportedException();
        }

        private XDocument GenerateWorkItemChangedEventXml(object notificationEventArgs)
        {
            var eventArgs = notificationEventArgs as WorkItemChangedEvent;
            if (eventArgs == null)
            {
                return null;
            }

            // Serialize the event before manipulating since it is passed in by the TFS.
            var eventDocument = workItemChangedEventSerializer.SerializeToXml(notificationEventArgs);

            var titleNode = eventDocument.Root.Descendants("Title").Single();

            XElement customFieldsElement = this.RetrieveCustomFields(eventArgs);

            eventDocument.Root.Add(customFieldsElement);

            if (eventArgs.ChangeType == ChangeTypes.Change)
            {
                titleNode.Value = string.Format(CultureInfo.InvariantCulture, "{0} Work Item Changed: {1} {2} - {3}", new object[] { eventArgs.PortfolioProject, eventArgs.CoreFields.StringFields[0].NewValue, eventArgs.CoreFields.IntegerFields[0].NewValue, eventArgs.WorkItemTitle });
            }
            else
            {
                titleNode.Value = string.Format(CultureInfo.InvariantCulture, "{0} Work Item Created: {1} {2} - {3}", new object[] { eventArgs.PortfolioProject, eventArgs.CoreFields.StringFields[0].NewValue, eventArgs.CoreFields.IntegerFields[0].NewValue, eventArgs.WorkItemTitle });
            }

            return eventDocument;
        }

        private XElement RetrieveCustomFields(WorkItemChangedEvent eventArgs)
        {
            var workItemId = eventArgs.CoreFields.IntegerFields[0].NewValue;
            var workItemRev = eventArgs.CoreFields.IntegerFields[1].NewValue;
            var dal = new DataAccessLayerImpl(this.requestContext);

            var xml = dal.GetWorkItemXml(this.requestContext.UserContext.Identifier, workItemId, workItemRev, CultureInfo.CurrentCulture.Name);
            var nodes = xml.DocumentElement.SelectNodes("RevisionFields/Field[not(starts-with(@ReferenceName, 'System.'))]");

            var integerFieldsElement = new XElement("IntegerFields");
            var stringFieldsElement = new XElement("StringFields");
            var customFieldsElement = new XElement("CustomFields", integerFieldsElement, stringFieldsElement);

            foreach (XmlElement node in nodes)
            {
                var fieldElement = new XElement(
                    "Field",
                    new XElement("Name", node.Attributes["Name"].Value),
                    new XElement("ReferenceName", node.Attributes["ReferenceName"].Value),
                    new XElement("Value", node.FirstChild.InnerText));

                if (node.Attributes["Type"].Value == "Integer")
                {
                    integerFieldsElement.Add(fieldElement);
                }
                else
                {
                    stringFieldsElement.Add(fieldElement);
                }
            }

            return customFieldsElement;
        }

        private XDocument GenerateBuildCompletedEventXml(object notificationEventArgs)
        {
            var notification = notificationEventArgs as BuildCompletionNotificationEvent;
            if (notification == null)
            {
                return null;
            }

            var linkService = this.requestContext.GetService<TswaServerHyperlinkService>();
            var locationService = this.requestContext.GetService<TeamFoundationLocationService>();

            var build = notification.Build;

            var buildCompletionEvent = new BuildCompletionEvent()
            {
                BuildUri = build.Uri,
                TeamFoundationServerUrl = locationService.GetHostLocation(this.requestContext, locationService.PublicAccessMapping),
                TeamProject = build.TeamProject,
                Url = linkService.GetViewBuildDetailsUrl(new Uri(build.Uri)).ToString(),
                Id = build.BuildNumber,
                Configuration = build.Definition.Name,
                BuildStartTime = build.StartTime.ToLocalTime().ToString(),
                BuildCompleteTime = build.FinishTime.ToLocalTime().ToString(),
                CompletionStatus = Helper.Localize(build.Status),
                TimeZone = build.FinishTime.ToLocalTime().GetLocalTimeZoneName(),
                TimeZoneOffset = build.FinishTime.ToLocalTime().GetLocalTimeZoneOffset(),
                RequestedBy = String.IsNullOrWhiteSpace(build.RequestedFor) ? build.RequestedBy : build.RequestedFor,
                BuildMachine = this.GetBuildMachine(build.BuildControllerUri)
            };

            buildCompletionEvent.Title = String.Format(StringResources.BuildCompletionEmailTitle, buildCompletionEvent.TeamProject, buildCompletionEvent.Id, buildCompletionEvent.CompletionStatus);

            return buildCompletionEventSerializer.SerializeToXml(buildCompletionEvent);
        }

        private XDocument GenerateBuildCompletionEvent2Xml(object notificationEventArgs)
        {
            var notification = notificationEventArgs as BuildCompletionNotificationEvent;
            if (notification == null)
            {
                return null;
            }

            var linkService = this.requestContext.GetService<TswaServerHyperlinkService>();
            var locationService = this.requestContext.GetService<TeamFoundationLocationService>();

            var build = notification.Build;

            var buildCompletionEvent = new BuildCompletionEvent2()
            {
                TeamFoundationServerUrl = locationService.GetHostLocation(this.requestContext, locationService.PublicAccessMapping),
                TeamProject = build.TeamProject,
                BuildUri = build.Uri,
                AgentPath = this.GetBuildMachine(build.BuildControllerUri),
                DefinitionPath = build.Definition.FullPath,
                BuildNumber = build.BuildNumber,
                StartTime = build.StartTime.ToLocalTime().ToString(),
                FinishTime = build.FinishTime.ToLocalTime().ToString(),
                Quality = build.Quality,
                Status = Helper.Localize(build.Status),
                StatusCode = build.Status.ToString(),
                CompilationStatus = Helper.Localize(build.CompilationStatus),
                TestStatus = Helper.Localize(build.TestStatus),
                LogLocation = build.LogLocation,
                DropLocation = build.DropLocation,
                SourceGetVersion = build.SourceGetVersion,
                RequestedFor = build.RequestedFor,
                RequestedBy = build.RequestedBy,
                KeepForever = build.KeepForever,
                TimeZone = build.FinishTime.ToLocalTime().GetLocalTimeZoneName(),
                TimeZoneOffset = build.FinishTime.ToLocalTime().GetLocalTimeZoneOffset(),
                Url = linkService.GetViewBuildDetailsUrl(new Uri(build.Uri)).ToString()
            };

            switch (build.Status)
            {
                case BuildStatus.Succeeded:
                    buildCompletionEvent.Title = String.Format(StringResources.BuildCompleted_Successful, buildCompletionEvent.TeamProject, buildCompletionEvent.BuildNumber);
                    break;

                case BuildStatus.PartiallySucceeded:
                    buildCompletionEvent.Title = String.Format(StringResources.BuildCompleted_PartiallySuccessful, buildCompletionEvent.TeamProject, buildCompletionEvent.BuildNumber);
                    break;

                case BuildStatus.Failed:
                    buildCompletionEvent.Title = String.Format(StringResources.BuildCompleted_Failed, buildCompletionEvent.TeamProject, buildCompletionEvent.BuildNumber);
                    break;

                case BuildStatus.Stopped:
                    buildCompletionEvent.Title = String.Format(StringResources.BuildCompleted_Canceled, buildCompletionEvent.TeamProject, buildCompletionEvent.BuildNumber);
                    break;

                default:
                    buildCompletionEvent.Title = String.Format(StringResources.BuildCompleted_Unknown, buildCompletionEvent.TeamProject, buildCompletionEvent.BuildNumber, buildCompletionEvent.Status);
                    break;
            }

            return buildCompletionEvent2Serializer.SerializeToXml(buildCompletionEvent);
        }

        private string GetBuildMachine(string buildControllerUri)
        {
            var buildResourceService = this.requestContext.GetService<TeamFoundationBuildResourceService>();
            var controllerQueryResult = buildResourceService.QueryBuildControllersByUri(this.requestContext, new string[] { buildControllerUri }, false);

            var controller = controllerQueryResult.Controllers.FirstOrDefault();

            return controller == null ? String.Empty : controller.Name;
        }
    }
}
